﻿using System;
using System.Text;

namespace SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra
{
    /// <summary>
    /// Класс вектора
    /// </summary>
    public class Vector
    {
        private double[] _components;

        /// <summary>
        /// Возвращает элементы вектора
        /// </summary>
        internal double[] Components
        {
            get 
            { 
                return _components; 
            }
        }

        /// <summary>
        /// Устанавливает элементы вектора
        /// </summary>
        /// <param name="components"></param>
        protected void SetComponents(double[] components)
        {
            _components = components;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="vectorComponents"></param>
        public Vector(double[] vectorComponents)
        {
            if (vectorComponents.Length <= 0)
                throw new IndexOutOfRangeException();
            _components = new double[vectorComponents.Length];
            vectorComponents.CopyTo(_components, 0);
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="dimensionality"></param>
        public Vector(int dimensionality)
        {
            if (dimensionality <= 0)
                throw new ArgumentOutOfRangeException("dimensionality");
            _components = new double[dimensionality];            
        }

        /// <summary>
        /// Прибавляет указанный вектор
        /// </summary>
        /// <param name="secondVector"></param>
        public void Add(double[] secondVector)
        {
            if (_components.Length != secondVector.Length)
                throw new BIVectorCantAddOrSubtractException(_components.Length, secondVector.Length, null);
            for (int i = 0; i < _components.Length; i++)
                _components[i] += secondVector[i];
        }

        /// <summary>
        /// Прибавляет указанный вектор
        /// </summary>
        /// <param name="secondVector"></param>
        public void Add(Vector secondVector)
        {
            Add(secondVector._components);
        }

        /// <summary>
        /// Вычитывает указанный вектор
        /// </summary>
        /// <param name="secondVector"></param>
        public void Subtract(double[] secondVector)
        {
            if (_components.Length != secondVector.Length)
                throw new BIVectorCantAddOrSubtractException(_components.Length, secondVector.Length, null);
            for (int i = 0; i < _components.Length; i++)
                _components[i] -= secondVector[i];
        }

        /// <summary>
        /// Вычитывает указанный вектор
        /// </summary>
        /// <param name="secondVector"></param>
        public void Subtract(Vector secondVector)
        {
            Subtract(secondVector._components);
        }

        /// <summary>
        /// Возвращает вектор, полученные суммированием двух векторов
        /// </summary>
        /// <param name="W"></param>
        /// <param name="V"></param>
        /// <returns></returns>
        public static Vector operator+(Vector W, Vector V)
        {
            int n = W.Dimensionality;
            if (n != V.Dimensionality)
                throw new BIVectorCantAddOrSubtractException(n, V.Dimensionality, null);
            double[] newComponents = new double[n];
            for (int i = 0; i < n; i++)
                newComponents[i] = W._components[i] + V._components[i];
            return new Vector(newComponents);
        }

        /// <summary>
        /// Очищает вектор
        /// </summary>
        public void Clear()
        {
            Array.Clear(_components, 0, _components.Length);
        }

        /// <summary>
        /// Возвращает указанный элемент вектора
        /// </summary>
        /// <param name="componentIndex"></param>
        /// <returns></returns>
        public double this[int componentIndex]
        {
            get 
            { 
                return _components[componentIndex]; 
            }
        }

        /// <summary>
        /// Возвращает размерность вектора
        /// </summary>
        public int Dimensionality
        {
            get 
            { 
                return _components.Length; 
            }
        }
        
        public bool Equals(Vector secondVector)
        {
            if (secondVector.Dimensionality != _components.Length)
                return false;
            for (int i = 0; i < _components.Length; i++)
            {
                if (secondVector._components[i] != _components[i])
                    return false;
            }
            return true;
        }

        public override bool Equals(object o)
        {
            Vector v = o as Vector;
            return v != null ? this.Equals(v) : false;
        }

        public override int GetHashCode()
        {
            return _components.GetHashCode();
        }

        public static bool operator==(Vector W, Vector V)
        {
            return W.Equals(V);
        }

        public static bool operator!=(Vector W, Vector V)
        {
            return !W.Equals(V);
        }

        /// <summary>
        /// Возвращает норму вектора
        /// </summary>
        public double GetNorm()
        {            
            double sum = 0;
            for (int i = 0; i < _components.Length; i++)
                sum += _components[i] * _components[i];
            return Math.Sqrt(sum);            
        }

        /// <summary>
        /// Возвращает вектор, полученный умножением вектора на коэффициент
        /// </summary>
        /// <param name="W"></param>
        /// <param name="coefficient"></param>
        /// <returns></returns>
        public static Vector operator*(Vector W, double coefficient)
        {
            double[] newComponents = new double[W.Dimensionality];
            for (int i = 0; i < W.Dimensionality; i++)
                newComponents[i] = coefficient * W._components[i];
            return new Vector(newComponents);
        }

        /// <summary>
        /// Возвращает вектор, полученный умножением вектора на коэффициент
        /// </summary>
        /// <param name="W"></param>
        /// <param name="V"></param>
        /// <returns></returns>
        public static double operator*(Vector W, Vector V)
        {
            if (W.Dimensionality != V.Dimensionality)
                throw new BIVectorCantMultiplyVectorException(W.Dimensionality, V.Dimensionality, null);
            return W.GetDotProduct(V);
        }

        /// <summary>
        /// Возвращает вектор, полученный умножением вектора на матрицу
        /// </summary>
        /// <param name="W"></param>
        /// <param name="A"></param>
        /// <returns></returns>
        public static Vector operator*(Vector W, Matrix A)
        {
            int n = A.RowsCount;
            int m = A.ColumnsCount;
            if (W.Dimensionality != n)
                throw new BIMatrixCantMultiplyMatrixException(W.Dimensionality, n, null);                            
            return W.GetResultOfSelfTransposedMultiplication(A);
        }

        /// <summary>
        /// Масштабирует вектор в указанное число раз
        /// </summary>
        /// <param name="times"></param>        
        public void Scale(double times)
        {
            for (int i = 0; i < _components.Length; i++)
                _components[i] *= times;            
        }

        /// <summary>
        /// Скалярно умножает на указанный вектор
        /// </summary>
        /// <param name="secondVector"></param>
        /// <returns></returns>
        internal protected double GetDotProduct(Vector secondVector)
        {
            double sum = 0;
            for (int i = 0; i < _components.Length; i++)
                sum += _components[i] * secondVector._components[i];
            return sum;
        }

        /// <summary>
        /// Возвращает вектор, полученный произведением собственного транспонированного вектора на указанную матрицу
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        internal protected Vector GetResultOfSelfTransposedMultiplication(Matrix secondMatrix)
        {
            int n = secondMatrix.RowsCount;
            int m = secondMatrix.ColumnsCount;
            double[] vectorComponents = new double[m];
            for (int j = 0; j < m; j++)
            {                
                for (int i = 0; i < n; i++)
                    vectorComponents[j] += _components[i] * secondMatrix.Components[i, j];
            }
            return new Vector(vectorComponents);
        }

        /// <summary>
        /// Возвращает вектор разности двух векторов
        /// </summary>
        /// <param name="W"></param>
        /// <param name="V"></param>
        /// <returns></returns>
        public static Vector operator-(Vector W, Vector V)
        {
            if (W.Dimensionality != V.Dimensionality)
                throw new BIVectorCantAddOrSubtractException(W.Dimensionality, V.Dimensionality, null);
            double[] newComponents = new double[W.Dimensionality];
            for (int i = 0; i < W.Dimensionality; i++)
                newComponents[i] = W._components[i] - V._components[i];
            return new Vector(newComponents);
        }

        /// <summary>
        /// Возвращает матрицу, полученную тензорным произведением на указанный вектор
        /// </summary>
        /// <param name="secondVector"></param>
        /// <returns></returns>
        public Matrix GetTensorProduct(Vector secondVector)
        {
            int n = _components.Length;
            int m = secondVector.Dimensionality;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                    newComponents[i, j] = _components[i] * secondVector._components[j];
            }
            return n == m ? new SymmetricMatrix(newComponents)
                                  : new Matrix(newComponents);
        }

        /// <summary>
        /// Возвращает клонированные элементы вектора
        /// </summary>
        /// <returns></returns>
        public double[] CloneComponents()
        {
            double[] result = new double[_components.Length];
            _components.CopyTo(result, 0);
            return result;
        }

        /// <summary>
        /// Возвращает вектор как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            char[] separator = { '[', ' ' };
            for (int i = 0; i < _components.Length; i++)
            {
                sb.Append(separator);
                sb.Append(_components[i]);
                if (i == 0) 
                    separator[0] = ',';
            }
            sb.Append(']');
            return sb.ToString();
        }
    }
}
